﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Nature.Toolkit;

namespace Nature.Data
{
    public class ChemicalKineticsRuntimeTextTemplateSchema
    {
        internal struct SriIndexes
        {
            public int ABCPartIndex { get; set; }

            public int? EPartIndex { get; set; }
        }

        readonly internal AH2004ModelObject Model;
        public readonly int Nsp;
        public readonly int Nre;

        readonly Dictionary<string, int> m_tempDepParts = new Dictionary<string, int>();
        readonly Dictionary<ArrheniusParameters, int> m_arrheniusTemperatureFunctions = new Dictionary<ArrheniusParameters, int>();
        internal readonly Dictionary<TroeParameters, int> TroeTemperatureFunctions = new Dictionary<TroeParameters, int>();
        internal readonly Dictionary<SriParameters, SriIndexes> SriTemperatureFunctions = new Dictionary<SriParameters, SriIndexes>();
        internal readonly Dictionary<ChemicalReaction, int> EquilibriumConstantTemperatureFunctions = new Dictionary<ChemicalReaction, int>();

        internal ChemicalKineticsRuntimeTextTemplateSchema(AH2004ModelObject model)
        {
            Model = model;
            Nsp = model.Nsp;
            Nre = model.Nre;
            var reactions = model.Entities.OfType<ChemicalReaction>().OrderBy(i => i.Ordinal).ToList();
            foreach (var reac in reactions)
            {
                RegisterArrheniusTempDepPart(reac.Fwd);
                RegisterArrheniusTempDepPart(reac.Rev);
                RegisterArrheniusTempDepPart(reac.Low);
                RegisterTroeTempDepPart(reac.Troe);
                RegisterSriTempDepPart(reac.Sri);
                RegisterEquilibriumConstantTempDepPart(reac);
            }
        }


        public int Ntd
        {
            get { return m_tempDepParts.Count; }
        }

        public string[] ExplicitTemperatureFunctions
        {
            get { return m_tempDepParts.Keys.ToArray();  }
        }


        void RegisterArrheniusTempDepPart(ArrheniusParameters k)
        {
            if (k == null || k.IsConstant) { return; }
            StringBuilder sb = new StringBuilder();
            if (k.B != 0d && k.C != 0d)
            {
                sb.AppendFormat("Math.Exp(({0:e12}) * logT - ({1:e12}) / t )", k.B, k.C);
            }
            else if (k.B != 0d)
            {
                sb.Append(DynamicCodeHelper.Pow("t", k.B));
            }
            else
            {
                sb.AppendFormat("Math.Exp(-({0:e12}) / t)", k.C);
            }
            m_arrheniusTemperatureFunctions[k] = RegisterTempDepCode(sb.ToString());
        }

        void RegisterTroeTempDepPart(TroeParameters troe)
        {
            if (troe == null) { return; }
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("({0:e12}) * Math.Exp(-t / ({1:e12}))", 1.0 - troe.A, troe.B);
            sb.AppendFormat(" + ({0:e12}) * Math.Exp(-t / ({1:e12}))", troe.A, troe.C);
            if (troe.D.HasValue)
            {
                sb.AppendFormat(" + Math.Exp(-({0:e12}) / t)", troe.D.Value);
            }
            string code = string.Format("Math.Log10( 1.0e-300 + {0} )", sb.ToString());
            TroeTemperatureFunctions[troe] = RegisterTempDepCode(code);
        }

        private void RegisterSriTempDepPart(SriParameters sri)
        {
            if (sri == null) { return; }
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("{0:e12} * Math.Exp(-({1:e12}) / t) + Math.Exp(-t / ({2:e12}))", sri.A, sri.B, sri.C);
            SriIndexes indexes = new SriIndexes();
            indexes.ABCPartIndex = RegisterTempDepCode(sb.ToString());
            if (sri.E.HasValue)
            {
                string code = DynamicCodeHelper.Pow("t", sri.E.Value);
                indexes.EPartIndex = RegisterTempDepCode(code);
            }
            SriTemperatureFunctions[sri] = indexes;
        }


        void RegisterEquilibriumConstantTempDepPart(ChemicalReaction r)
        {
            if (false == r.IsReversible || r.Rev != null) { return; }
            double delta;
            StringBuilder sb = new StringBuilder();
            int counter = 0;
            foreach (var reagent in r.Reagents.OrderByDescending(i => i.RevCoefficient))
            {
                delta = reagent.RevCoefficient - reagent.FwdCoefficient;
                if (delta == 0.0) { continue; }
                sb.Append(counter++ > 0 ? " + " : string.Empty);
                sb.AppendFormat("({0:e12}) * g[{1}]", delta, Model.GetSpeciesIndex(reagent.SpeciesID));
            }
            delta = r.MolesPerActProduction;
            string code =
                delta == 0.0
                ? string.Format("Math.Exp({0})", sb.ToString())
                : string.Format("{0} * Math.Exp({1})", DynamicCodeHelper.Pow("c0", (-delta)), sb.ToString());

            int index = RegisterTempDepCode(code);
            EquilibriumConstantTemperatureFunctions[r] = index;
        }

        int RegisterTempDepCode(string code)
        {
            if (m_tempDepParts.ContainsKey(code)) { return m_tempDepParts[code]; }
            int index = m_tempDepParts.Count;
            m_tempDepParts[code] = index;
            return index;
        }


        internal string Write(ArrheniusParameters k)
        {
            if (k == null) { return null; }
            StringBuilder sb = new StringBuilder(k.A.ToString());
            int tdeplIndex = -1;
            if (true == m_arrheniusTemperatureFunctions.TryGetValue(k, out tdeplIndex))
            {
                sb.AppendFormat(" * tdep[{0}]", tdeplIndex);
            }
            return sb.ToString();
        }

        internal string Write(IEnumerable<Enhanced> enhanced)
        {
            if (enhanced == null || false == enhanced.Any()) { return null; }
            StringBuilder sb = new StringBuilder("1.0");
            foreach (var e in enhanced)
            {
                sb.AppendFormat(" + x[{0}] * ({1})", Model.GetSpeciesIndex(e.SpeciesID), e.Coefficient - 1.0);
            }
            return string.Format("({0})", sb.ToString());
        }


        public IChemicalKinetics ToChemicalKineticsObject()
        {
            string[] sources = new string[] 
            {
                new ChemicalKineticsRuntimeTextTemplate(this).TransformText(),
                new CkExplicitTemperatureDependenciesRuntimeTextTemplate(this).TransformText(),
                new CkReactionForwardConstantsRuntimeTextTemplate(this).TransformText(),
                new CkReactionReverseConstantsRuntimeTextTemplate(this).TransformText(),
                new CkReactionForwardRatesRuntimeTextTemplate(this).TransformText(),
                new CkReactionReverseRatesRuntimeTextTemplate(this).TransformText(),
                new CkSpeciesCreationRatesRuntimeTextTemplate(this).TransformText(),
                new CkSpeciesDestructionRatesRuntimeTextTemplate(this).TransformText()
            };
            var compilerParameters = new CompilerParameters() 
            {
                GenerateExecutable = false,
                GenerateInMemory = true,
                CompilerOptions = "/optimize"
            };
            compilerParameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
            Assembly assembly = DynamicAssemblyUtils.GetBySourceCodes(compilerParameters, sources);
            object chemicalKineticsObject = assembly.CreateInstance("Nature.ChemicalKinetics");
            return (IChemicalKinetics)chemicalKineticsObject;
        }
        
    }
}
